<?php
/**
 * @Author: gan
 * @Description:
 * @File:  SnowFlakeHelper
 * @Version: 1.0.0
 * @Date: 2023/4/21 8:16 下午
 */
declare(strict_types=1);

namespace RX\Helpers;

use Swoole\Coroutine;

class SnowFlakeHelper
{
    private static int $lastTimestamp = 0;
    private static int $lastSequence  = 0;
    private static int $sequenceMask  = 2047;
    private static int $startStamp    = 1680537600000;

    /**
     * 生成基于雪花算法的随机编号
     * @param int $dataCenterID 数据中心ID 0-31
     * @param int $workerID 任务进程ID 0-127
     * @param int $startStamp 开始时间戳(毫秒)
     * @return int 分布式ID
     */
    public static function make(int $dataCenterID = 0, int $workerID = 0, int $startStamp = 0)
    {
        $startStamp = $startStamp > 0 ? $startStamp : self::$startStamp;

        if ($dataCenterID > 31) {
            throw new \InvalidArgumentException('dataCenterId must between 0-31');
        }

        if ($workerID > 127) {
            throw new \InvalidArgumentException('dataCenterId must between 0-127');
        }
        // 41bit timestamp + 5bit dataCenterId + 7bit workerId + 11bit lastSequence
        $timestamp = self::timeGen();
        if (self::$lastTimestamp == $timestamp) {
            self::$lastSequence = (self::$lastSequence + 1) & self::$sequenceMask;
            if (self::$lastSequence == 0) {
                $timestamp = self::tilNextMillis(self::$lastTimestamp);
            }
        } else {
            self::$lastSequence = 0;
        }

        self::$lastTimestamp = (int)$timestamp;
        return (($timestamp - $startStamp) << 22) | ($dataCenterID << 17) | ($workerID << 10) | self::$lastSequence;
    }

    private static function tilNextMillis($lastTimestamp)
    {
        $timestamp = self::timeGen();
        while ($timestamp <= $lastTimestamp) {
            $cid = Coroutine::getCid();
            if ($cid > 0) {
                Coroutine::sleep(0.001);
            } else {
                usleep(1);
            }
            $timestamp = self::timeGen();
        }
        return $timestamp;
    }

    private static function timeGen()
    {
        return (float)sprintf('%.0f', microtime(true) * 1000);
    }
}
